// Copyright 2013 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2013 Petar Maymounkov <p@gocircuit.org>

package ns

import (
	"fmt"
	"reflect"
	"runtime"
	"strconv"
	"sync"
	"time"
	"unsafe"

	"circuit/kit/rh"
)

// MapDir
type MapDir interface {
	Map() map[string]Dir
}

var typeOfMapDir = reflect.TypeOf(new(MapDir)).Elem()

// funcMapDir
type funcMapDir struct {
	f func() map[string]Dir
}

func (x funcMapDir) Map() map[string]Dir {
	return x.f()
}

// VarMapDir
type VarMapDir map[string]Dir

func (x VarMapDir) Map() map[string]Dir {
	return (map[string]Dir)(x)
}

// SliceDir
type SliceDir interface {
	Slice() []Dir
}

var typeOfSliceDir = reflect.TypeOf(new(SliceDir)).Elem()

// funcSliceDir
type funcSliceDir struct {
	f func() []Dir
}

func (x funcSliceDir) Slice() []Dir {
	return x.f()
}

// VarSliceDir
type VarSliceDir []Dir

func (x VarSliceDir) Slice() []Dir {
	return ([]Dir)(x)
}

//
func newSliceFID(name string, recv SliceDir) *mapFID {
	return newMapFID(name, mapSliceDir{recv})
}

type mapSliceDir struct {
	usr SliceDir
}

func (msd mapSliceDir) Map() map[string]Dir {
	r := make(map[string]Dir)
	for i, d := range msd.usr.Slice() {
		r[strconv.Itoa(i)] = d
	}
	return r
}

// mapFID
type mapFID struct {
	rh.ReadOnlyFID
	//
	name     string
	recv     MapDir
	mtime    time.Time
	__       sync.Mutex
	__atime  time.Time
	__parent rh.FID
}

func stack() string {
	buf := make([]byte, 1024)
	return string(buf[:runtime.Stack(buf, false)])
}

func newMapFID(name string, recv MapDir) *mapFID {
	now := time.Now()
	mfid := &mapFID{
		name:    name,
		recv:    recv,
		mtime:   now,
		__atime: now,
	}
	return mfid
}

func (fid *mapFID) attach(parent rh.FID) {
	fid.__.Lock()
	defer fid.__.Unlock()
	fid.__parent = parent
}

func (fid *mapFID) String() string {
	return fmt.Sprintf("mapfid·%s:(%02x)", fid.name, uint64(uintptr(unsafe.Pointer(fid)))&0xff)
}

func (fid *mapFID) Q() rh.Q {
	return rh.Q{
		ID:  interfaceHash(fid.recv),
		Ver: 1,
	}
}

func (fid *mapFID) Open(flag rh.Flag, _ rh.Intr) error {
	return nil
}

func (fid *mapFID) Clunk() {}

func (fid *mapFID) Stat() (*rh.Dir, error) {
	fid.__.Lock()
	defer fid.__.Unlock()
	x, err := fid.callmap()
	if err != nil {
		return nil, err
	}
	return &rh.Dir{
		Q: fid.Q(),
		Mode: rh.Mode{
			Attr:     rh.ModeDir,
			IsHidden: x == nil,
		},
		Perm:   0555, // r-xr-xr-x
		Atime:  fid.__atime,
		Mtime:  fid.mtime,
		Name:   fid.name,
		Length: int64(len(x)),
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}

func (fid *mapFID) callmap() (x map[string]Dir, err error) {
	defer func() {
		if r := recover(); r != nil {
			x, err = nil, rh.ErrIO
		}
	}()
	return fid.recv.Map(), nil
}

func (fid *mapFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) == 0 {
		return fid, nil
	}
	y, err := fid.callmap()
	if err != nil {
		return nil, err
	}
	x, ok := y[wname[0]]
	if !ok {
		return nil, rh.ErrNotExist
	}
	return NewFID(wname[0], x).Walk(wname[1:])
}

func (fid *mapFID) Read(offset int64, count int, _ rh.Intr) (chunk rh.Chunk, err error) {
	//
	md, err := fid.callmap()
	if err != nil {
		return nil, err
	}
	//
	r := make(rh.DirChunk, 0, len(md))
	for name, dir := range md {
		d, _ := NewFID(name, dir).Stat()
		r = append(r, d)
	}
	return r, nil
}
